using System;
using System.Linq;
using System.Windows;
using System.Windows.Input.Manipulations;
using System.Windows.Interactivity;

namespace Wintellect.Touch
{
    /// <summary>
    /// Provides an listener class that interprets touch events and promotes them to gesture events
    /// </summary>
    /// <remarks>
    /// The events that are raised from the partial implementation of this class are imported from the 
    /// Silverlight for Windows Phone Toolkit.  Information about that license is listed in the implementation file.
    /// </remarks>
    public partial class GestureListener
    {
        #region Fields

        private FrameworkElement _listenedElement;
        private GestureAction _currentGestureAction;

        #endregion

        #region Constructor(s) and Initialization

        /// <summary>
        /// Initializes a new instance of the <see cref="GestureListener"/> class.
        /// </summary>
        public GestureListener()
        {
        }

        /// <summary>
        /// Sets element on which Mouse events will be listened to.  Without setting this, the listener will not work.
        /// </summary>
        /// <param name="element">The element.</param>
        public void SetListenedElement(FrameworkElement element)
        {
            if (element == null) throw new ArgumentNullException("element");
            if (_listenedElement != null) throw new InvalidOperationException("The current implementation does not allow for resetting the listened element");
            _listenedElement = element;

            var behaviors = Interaction.GetBehaviors(element);
            var manipulationBehavior = behaviors.FirstOrDefault(x => x is Manipulate2DBehavior) as Manipulate2DBehavior;
            if (manipulationBehavior == null)
            {
                manipulationBehavior = new Manipulate2DBehavior { IsInertial = false };
                behaviors.Add(manipulationBehavior);
            }

            manipulationBehavior.ManipulationStarted += OnManipulationStarted;
            manipulationBehavior.ManipulationDelta += OnManipulationDelta;
            manipulationBehavior.ManipulationCompleted += OnManipulationCompleted;

            //element.MouseLeftButtonDown += OnMouseDown;
            //element.MouseMove += OnMouseMove;
            //element.MouseLeftButtonUp += OnMouseUp;
        } 

        #endregion

        #region Manipulation & Action Event Handlers

        private void OnManipulationStarted(Object sender, Manipulation2DStartedEventArgs e)
        {
            EnsureInitialized();

            if (_currentGestureAction == null)
            {
                _currentGestureAction = new TouchGestureAction(_listenedElement);
                _currentGestureAction.ActionCompleted += HandleCurrentGestureActionCompleted;
            }

            if (_currentGestureAction.Initialize())
            {
                var currentPosition = new Point(e.OriginX, e.OriginY);
                _currentGestureAction.HandleSingleManipulationStarted(currentPosition);
            }
        }

        private void OnManipulationDelta(Object sender, Manipulation2DDeltaEventArgs e)
        {
            EnsureInitialized();

            if (_currentGestureAction != null && _currentGestureAction.IsValid)
            {
                if (e.Delta.ExpansionX == 0 && e.Delta.ExpansionY == 0)
                {
                    var currentPosition = new Point(e.OriginX + e.Cumulative.TranslationX, e.OriginY + e.Cumulative.TranslationY);
                    var offsetFromOrigin = new Point(e.Cumulative.TranslationX, e.Cumulative.TranslationY);
                    _currentGestureAction.HandleSingleManipulationDelta(currentPosition, offsetFromOrigin);
                }
                else
                {
                    var currentPosition = new Point(e.OriginX + e.Cumulative.TranslationX, e.OriginY + e.Cumulative.TranslationY);
                    var scaleDelta = new Point(e.Delta.ScaleX, e.Delta.ScaleY);
                    _currentGestureAction.HandleMultipleManipulationDelta(currentPosition, scaleDelta);
                }
            }
        }

        private void OnManipulationCompleted(Object sender, Manipulation2DCompletedEventArgs e)
        {
            EnsureInitialized();

            if (_currentGestureAction != null && _currentGestureAction.IsValid)
            {
                _currentGestureAction.Release();

                var currentPosition = new Point(e.OriginX + e.Total.TranslationX, e.OriginY + e.Total.TranslationY);
                _currentGestureAction.HandleManipulationCompleted(currentPosition);
            }
        }

        private void HandleCurrentGestureActionCompleted(Object sender, EventArgs e)
        {
            if (_currentGestureAction != null)
            {
                _currentGestureAction.ActionCompleted -= HandleCurrentGestureActionCompleted;
                _currentGestureAction.Release();
                _currentGestureAction = null;
            }
        }

        #endregion

        #region Helpers

        private void EnsureInitialized()
        {
            if (_listenedElement == null) throw new InvalidOperationException("The Gesture Listener must be initialized before use by calling the SetListenedElement function");
        } 

        #endregion
    }
}